#define _USE_MATH_DEFINES

#include "Polygon.h"
using namespace model;

Polygon::Polygon(void)
{
}

Polygon::Polygon(PolygonOrigin reference)
{
	this->reference         = reference;
	center                  = calculateCenter();
	rotatingPoint           = calculateCenter();
}

Polygon::Polygon(std::vector<Point> &vertices, PolygonOrigin reference)
{
	setVertices(vertices);
	this->reference         = reference;
	center                  = calculateCenter();
	rotatingPoint           = calculateCenter();
	height                  = maxHeight();
	width                   = maxWidth();
}

Polygon::~Polygon(void)
{
	for_each(vertices.begin(), vertices.end(), [](Point*p){delete p;});
}

void Polygon::setReference(PolygonOrigin reference)
{
	this->reference = reference;
}

std::vector<Point*> Polygon::getVertices()
{
	return vertices;
}

void Polygon::addVertex(Point p)
{
	vertices.push_back(new Point(p));
	center        = calculateCenter();
	rotatingPoint = calculateCenter();
	height        = maxHeight();
	width         = maxWidth();
}

void Polygon::setRotatingPoint(Point point)
{
	rotatingPoint = point;
}

void Polygon::setVertices(std::vector<Point> &vertices)
{
	vector<Point>::iterator it;
	for (it=vertices.begin(); it!=vertices.end(); it++)
		this->vertices.push_back(new Point(*it));

	center        = calculateCenter();
	rotatingPoint = calculateCenter();
	height        = maxHeight();
	width         = maxWidth();
}

void Polygon::rotate(double angle)
{
	double realAngle = (360.00-angle)*M_PI/180.0;

	// Toma como punto de referencia para rotar el definido o sino el centro del poligono
	if (!rotatingPoint.isValid())
		rotatingPoint = calculateCenter();

	double newX = 0, newY = 0;
	vector<Point*>::iterator it;
	for (it=vertices.begin(); it!=vertices.end(); it++)
	{
		newX = cos(realAngle) * ((*it)->x() - rotatingPoint.x()) - sin(realAngle) * ((*it)->y() - rotatingPoint.y()) + center.x();
		newY = sin(realAngle) * ((*it)->x() - rotatingPoint.x()) + cos(realAngle) * ((*it)->y() - rotatingPoint.y()) + center.y();
	
		(*it)->setX(newX);
		(*it)->setY(newY);
	}

	rotatingPoint = center = calculateCenter();
}

void Polygon::expand(double ratio)
{
	double widthIncrement  = (maxWidth () * (ratio-1)) / 2;
	double heightIncrement = (maxHeight() * (ratio-1)) / 2;
	
	doExpand(widthIncrement, heightIncrement);
}

void Polygon::expand(double xRatio, double yRatio)
{
	double widthIncrement  = (maxWidth () * (xRatio-1)) / 2;
	double heightIncrement = (maxHeight() * (yRatio-1)) / 2;

	doExpand(widthIncrement, heightIncrement);
}

void Polygon::doExpand(double xRatio, double yRatio)
{
	if (!rotatingPoint.isValid())
		rotatingPoint = calculateCenter();

	double newX = 0, newY = 0;
	vector<Point*>::iterator it;
	for (it=vertices.begin(); it!=vertices.end(); it++)
	{
		newX = (*it)->x() < rotatingPoint.x() ? (*it)->x() - xRatio :  (*it)->x() + xRatio;
		newY = (*it)->y() < rotatingPoint.y() ? (*it)->y() - yRatio :  (*it)->y() + yRatio;
	
		(*it)->setX(newX);
		(*it)->setY(newY);
	}

	// actualiza la ubicacion del centro
	rotatingPoint = center = calculateCenter();
}

void Polygon::moveTo(double x, double y, PolygonOrigin from)
{
	double desplazamientoX = 0, desplazamientoY = 0;

	if (vertices.size() > 0)
	{
		if (from == P_TopLeft)
		{
			desplazamientoX = x - (center.x() - width/2);
			desplazamientoY = y - (center.y() - height/2);
		}
		else if (from == P_Center)
		{
			desplazamientoX = x - center.x();
			desplazamientoY = y - center.y();
		}
	}

	vector<Point*>::iterator it;
	for (it=vertices.begin(); it!=vertices.end(); it++)
	{
		// calcula la diferencia en el nuevo punto y el anterior y lo suma o resta a la posicion anterior (suma si se mueve a la derecha del punto, resta si se mueve a la izq)
		(*it)->setX((*it)->x() + desplazamientoX);
		(*it)->setY((*it)->y() + desplazamientoY);
	}

	// actualiza la ubicacion del centro
	rotatingPoint = center = calculateCenter();
}

Point Polygon::calculateCenter() const
{
	double xMin = 9999;
	double xMax = 0;
	double yMin = 9999;
	double yMax = 0;
	double xCenter = 0, yCenter = 0;

	if (vertices.size() == 3)
	{
		int sides = vertices.size();
		for (unsigned int i=0; i<vertices.size(); i++)	
		{
			xCenter += vertices[i]->x();		
			yCenter += vertices[i]->y();
		}

		xCenter = xCenter/sides;
		yCenter = yCenter/sides;
	}
	else
	{

		for (unsigned int i=0; i<vertices.size(); i++)
		{
			if (vertices[i]->x() < xMin)
				xMin = vertices[i]->x();
			if (vertices[i]->x() > xMax)
				xMax = vertices[i]->x();
			if (vertices[i]->y() < yMin)
				yMin = vertices[i]->y();
			if (vertices[i]->y() > yMax)
				yMax = vertices[i]->y();
		}

		xCenter = (xMin + xMax)/2;
		yCenter = (yMin + yMax)/2;
	}

	return Point(xCenter, yCenter);
}

int Polygon::maxWidth() const
{
	int maxWidth = 0;
	double auxDistanciaX = 0;

	for (unsigned int i=0; i<vertices.size(); i++)
	{
		for (unsigned int j=i; j<vertices.size(); j++)
		{
			auxDistanciaX = abs(vertices[j]->x() - vertices[i]->x());

			// guarda la mayor distancia
			if (auxDistanciaX > maxWidth)
				maxWidth = (int)auxDistanciaX;
		}
	}

	return maxWidth;
}

int Polygon::maxHeight() const
{
	int maxHeight = 0;
	double auxDistanciaY = 0;

	for (unsigned int i=0; i<vertices.size(); i++)
	{
		for (unsigned int j=i; j<vertices.size(); j++)
		{

			auxDistanciaY = abs(vertices[j]->y() - vertices[i]->y());

			if (auxDistanciaY > maxHeight)
				maxHeight = (int)auxDistanciaY;
		}
	}

	return maxHeight;
}

bool Polygon::containsPoint(const Point &p) const
{
	unsigned int  i = 0;
    int  j = vertices.size()-1;
    bool c = false;

	for (;i<vertices.size(); i++)
	{
		// http://arxiv.org/pdf/math/9410209v1.pdf
		// PRIMER OPERANDO
		//chequea q el y del punto sea mayor al primer vertice
		//chequea q el y del punto sea mayor al ultimo vertice
		//si ambos son TRUE, el punto esta por debajo de ambos
		//caso contrario esta por arriba de ambos
		//si uno es FALSE y otro TRUE, esta en el rango de esos 2 vertices que forman ademas un vector

		// SEGUNDO OPERANDO
		//chequea q el x del punto sea menor a: 
		//    (la distancia entre el ultimo vertice(x) y el primero(x))
		//  por
		//    (la distancia entre el punto (y) y el primer vertice(y))
		//  sobre
		//    (la distancia entre el ultimo vertice (y) y el primer vertice(y))
		//  mas
		//    el primer vertice(x)

		// el negar la variable booleana es como contar la cantidad de veces q corta un lado
		// negados impartes, da siempre FALSE, negados pares da siempre TRUE 
		// => impar esta fuera (FALSE)
		// => par esta adentro (TRUE)
		if (((vertices[i]->y() > p.y()) != (vertices[j]->y() > p.y())) && 
			((p.x() < (vertices[j]->x() - vertices[i]->x()) * (p.y() - vertices[i]->y()) / (vertices[j]->y() - vertices[i]->y()) + vertices[i]->x())))
		{
			c = !c;
		}
      
		j = i;
	}

	return c;
}